import torch
import torch.nn as nn
import torch.optim as optim

import numpy as np
import pandas as pd

torch.manual_seed(28)  # 随机种子

"""
鸢尾花数据的逻辑回归:根据4个特征数据判断鸢尾花的品种
    Sepal.Length
    Sepal.Width
    Petal.Length
    Petal.Width
    
    单层感知机模型
    
    x                   y
    
    o
    
                        o
    
    o
    
                        o
    
    o
    
                        o
    
    o
    
    
"""


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # [4,3]  torch.empty() 构建一个未初始化的张量，也就是张量中的元素值是未确定的，是内存里的随机值
        self.weight = nn.Parameter(torch.empty(4, 3))
        self.bias = nn.Parameter(torch.empty(1, 3))  # [1,3]
        self._init_weights()  # 初始化参数

    def _init_weights(self):
        # 遍历所有参数
        for _w in self.parameters():
            nn.init.normal_(_w)  # 生成符合高斯分布的初始随机参数，均值0，方差为1

    def forward(self, x):
        """
        前向传播
        :param x: [batch_size,4]：batch_size一个迭代批次中的样本个数
        :return:
        """

        return torch.matmul(x, self.weight) + self.bias


if __name__ == '__main__':
    df: pd.DataFrame = pd.read_csv("../data/iris.csv", sep=",")  # 读取数据
    # print(df.head(2)) # 打印前几行

    net = Net()  # 创建网络
    loss_fn = nn.CrossEntropyLoss()  # 定义损失函数
    opt = optim.SGD(net.parameters(), lr=0.01)  # 定义优化器

    # 开始训练
    total_epoch = 100  # 训练轮次
    batch_size = 20  # 一个轮次内的批次大小
    total_sample = len(df)  # 一共多少个样本

    # 一个轮次中的总批次数，//是整除，向下取整
    total_batch = 1 if batch_size >= total_sample else total_sample // batch_size

    for epoch in range(total_epoch):
        """
            np.random.permutation(x)
            如果 x 是整数，函数会生成一个从 0 到 x-1 的整数的随机排列,不重复。
            如果 x 是数组，函数会返回该数组的一个随机排列，而原数组不会被修改
        """
        total_random_index = np.random.permutation(total_sample)  # 0~total_sample 的数据索引排列
        for count_batch in range(total_batch):

            # 10 -》2  0：2 2：4 4：6
            # 0*batch_size:0*batch_size + batch_size
            # 1*batch_size:1*batch_size + batch_size
            # 2*batch_size:2*batch_size + batch_size
            si = count_batch * batch_size  # 起始索引
            ei = si + +batch_size  # 结束索引
            if count_batch == total_batch - 1:  # 最后一个批次读取剩下的所有数据,用于处理(总数 / 批次大小)除不尽的情形
                ei = total_sample
            _df: pd.DataFrame = df.loc[total_random_index[si:ei]]  # 通过 行标签数组 获取数据

            """
                "","Sepal.Length","Sepal.Width","Petal.Length","Petal.Width","Species"
                "1",5.1,3.5,1.4,0.2,"setosa"
                "2",4.9,3,1.4,0.2,"setosa"
                提取从第二列到第五列的数据，下标从0开始
            """
            x = torch.from_numpy(np.asarray(_df.iloc[:, 1:5])).to(torch.float32)  # 训练数据
            """
                提取 第六列 作为目标标签,并将其转换为 torch.Tensor 类型，数据类型为 long
            """
            y = torch.from_numpy(np.asarray(_df.iloc[:, 5])).to(torch.long)  # 训练数据对应的标签

            _y = net(x)  # 把数据传递给神经网络，得到预测结果
            _loss = loss_fn(_y, y)  # 计算这一批次数据的损失函数

            # 反向传播
            opt.zero_grad()  # 重置网络的梯度
            _loss.backward()
            opt.step()  # 执行优化，更新参数

            print(f"{epoch + 1}/{total_epoch} {count_batch + 1}/{total_batch} "
                  f"loss:{_loss.item():.3f}")
